﻿#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Reflection;
using Grawlix.Geometry;
using MbUnit.Framework;

namespace Grawlix.Tests
{
	///<summary>
	/// Utility functionality for unit tests.
	///</summary>
	public static class Utility
	{
		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static void AssertClose(float expected,
		                               float actual)
		{
			if (CheckClose(expected, actual))
				return;
			Assert.AreEqual(expected, actual);
			Assert.Fail("Test case failed; test function broken.");
		}

		///<summary>
		/// Fail unless a vector's values are within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static void AssertClose(Vector2 expected,
		                               Vector2 actual)
		{
			if (CheckClose(expected, actual))
				return;
			Assert.AreEqual(expected, actual);
			Assert.Fail("Test case failed; test function broken.");
		}

		///<summary>
		/// Fail unless a rotation's values are within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static void AssertClose(Rotation2 expected,
		                               Rotation2 actual)
		{
			if (CheckClose(expected, actual))
				return;
			Assert.AreEqual(expected, actual);
			Assert.Fail("Test case failed; test function broken.");
		}

		///<summary>
		/// Fail unless a matrix's values is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static void AssertClose(Matrix22 expected,
		                               Matrix22 actual)
		{
			if (CheckClose(expected, actual))
				return;
			Assert.AreEqual(expected, actual);
			Assert.Fail("Test case failed; test function broken.");
		}

		///<summary>
		/// Fail unless a transform structure's values is within a
		/// certain tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static void AssertClose(Transform2 expected,
		                               Transform2 actual)
		{
			if (CheckClose(expected, actual))
				return;
			Assert.AreEqual(expected, actual);
			Assert.Fail("Test case failed; test function broken.");
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static void AssertClose(Rect expected, Rect actual)
		{
			if (CheckClose(expected, actual))
				return;
			Assert.AreEqual(expected, actual);
			Assert.Fail("Test case failed; test function broken.");
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static void AssertClose(OrientedRect expected,
		                               OrientedRect actual)
		{
			if (CheckClose(expected, actual))
				return;
			Assert.AreEqual(expected, actual);
			Assert.Fail("Test case failed; test function broken.");
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static bool CheckClose(float expected, float actual)
		{
			if (expected == actual)
				return true;
			if (Math.Abs(expected) < 1e-5f)
				return (Math.Abs(actual) < 2e-5f);
			float test = (actual - expected) / expected;
			return Math.Abs(test) < .0001;
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static bool CheckClose(Vector2 expected,
		                              Vector2 actual)
		{
			return CheckClose(expected.X, actual.X) &&
			       CheckClose(expected.Y, actual.Y);
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static bool CheckClose(Matrix22 expected,
		                              Matrix22 actual)
		{
			return CheckClose(expected.X, actual.X) &&
			       CheckClose(expected.Y, actual.Y);
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static bool CheckClose(Rotation2 expected,
		                              Rotation2 actual)
		{
			return CheckClose(expected.Cos, actual.Cos) &&
			       CheckClose(expected.Sin, actual.Sin);
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static bool CheckClose(Transform2 expected,
		                              Transform2 actual)
		{
			return CheckClose(expected.Rotation, actual.Rotation) &&
			       CheckClose(expected.Displacement,
			                  actual.Displacement);
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static bool CheckClose(Rect expected, Rect actual)
		{
			return CheckClose(expected.Min, actual.Min) &&
			       CheckClose(expected.Max, actual.Max);
		}

		///<summary>
		/// Fail unless a floating point value is within a certain
		/// tolerance.
		///</summary>
		///<param name="expected">The value to expect.</param>
		///<param name="actual">The actual value received.</param>
		public static bool CheckClose(OrientedRect expected,
		                              OrientedRect actual)
		{
			return CheckClose(expected.Centroid, actual.Centroid) &&
			       CheckClose(expected.Dimensions, actual.Dimensions) &&
			       CheckClose(expected.Rotation, actual.Rotation);
		}

		///<summary>
		/// Create a new <see cref="AppDomain"/> to be used for
		/// testing.
		///</summary>
		///<returns>The new <see cref="AppDomain"/>.</returns>
		public static AppDomain CreateTestAppDomain()
		{
			string location = Assembly.GetCallingAssembly().Location;
			return AppDomain.CreateDomain("newDomain", null,
			                              AppDomain.CurrentDomain.
			                              	BaseDirectory, location,
			                              false);
		}
	}
}